package aladyn.generator;

import java.util.ArrayList;
import java.util.HashMap;

import aladyn.exceptions.LRGenerationException;
import javassist.*;

/**
 * Générateur de code des methodes "serialize" ou "deserialize"
 * @author Administrator YiHan Wang, Qi Xu, Bo CAI
 */
public class LRSerializableGenerator {
	
	/**
	 * Le loader de classe de Javassist.
	 */
	public static final ClassPool pool = ClassPool.getDefault();
	
	/**
	 * Le nom complet de l'interface LRSerializable
	 */
	public static final String serializeInterfaceName = "aladyn.interfaces.LRSerializable";
	
	/**
	 * Le nom complet de la classe Identificater
	 */
	public static final String IdentificaterClassName = "aladyn.id.Identificater";
	
	/**
	 * Le nom de la methode serialize de l'interface LRSerializable
	 */
	public static final String serializeMethodName = "serialize";
	
	/**
	 * Le nom de la methode deserialize de l'interface LRSerializable
	 */
	public static final String deserializeMethodName = "deserialize";
	
	/**
	 * Le délimiteur utilisé dans la serialisation
	 */
	public static final String delimiter = "\"@\"";
	
	/**
	 * La chaine qui represente le pointer null dans la serialisation
	 */
	public static final String serializedNull = "\"null\"";
	
	/**
	 * Le dossier qui contient les fichiers .class
	 */
	public static final String outputDir = "bin";
	
	/**
	 * Le tableau d'hachage qui contient les couple (nom de classe, numéro)
	 * On utilise le numéro distribué à la classe pour remplacer le nom de classe
	 */
	public static HashMap<String, Integer> hashTab = new HashMap<String, Integer>();
	
	protected static int classNumero = 0;
	
	/**
	 * Charger une class a partir d'un fichier .class
	 * @param classPath Chemin du fichier .class à charger
	 * @return CtClass de Javassist
	 * @throws NotFoundException
	 */
	public static CtClass LoadCtClass(String classPath) 
	throws NotFoundException {
		return pool.get(classPath);	
	}
	
	public static void addLRSerializable(String className) throws LRGenerationException{
		
		try {
			CtClass clazz = LoadCtClass(className);
			addLRSerializableInterface(clazz);
		} catch (NotFoundException e) {
			throw new LRGenerationException(e.getMessage());
		}
	}
	
	/**
	 * Ajouter l'interface "LRSerializable", ensuite générer et ajouter les méthodes de cette interface
	 */
	public static void addLRSerializableInterface(CtClass clazz) 
	throws LRGenerationException{
		if(clazz == null) 
			throw new LRGenerationException("Null pointer argument.");
		// String.isInterface() -> true ??? why
		if(clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() 
		   || clazz.isEnum() || clazz.isAnnotation())
			throw new LRGenerationException("Not a class." + clazz.isInterface());
		
		try {			
			CtClass ctLRSerialisableInterface = pool.get(serializeInterfaceName);
			if(hashTab.get(clazz.getName()) != null)
				return;
			
			if(!clazz.subtypeOf(ctLRSerialisableInterface)){
				clazz.addInterface(ctLRSerialisableInterface);
				ctLRSerialisableInterface.defrost();
			}
			
			Integer value = new Integer(getNextClassNumero());
			hashTab.put(clazz.getName(), value);
			////////////////////

			// verifie si les trois méthodes existent deja, sinon créer les
			CtMethod serializeMethod = null;
			CtMethod deserializeMethod1 = null;
			CtMethod deserializeMethod2 = null;
			
			CtClass ctStringTokenizer;
			CtClass ctString;
			CtClass ctObject;
			try {
				ctStringTokenizer = LoadCtClass("java.util.StringTokenizer");
				ctString = LoadCtClass("java.lang.String");
				ctObject = LoadCtClass("java.lang.Object");
			} catch (NotFoundException e2) {
				throw new LRGenerationException(e2.getMessage());
			}
			
			try {
				serializeMethod = clazz.getDeclaredMethod(serializeMethodName, 
														  new CtClass[0]);
			} catch (NotFoundException e1) {
				serializeMethod = new CtMethod(ctString, 
											   serializeMethodName, 
											   new CtClass[0], 
											   clazz);
				serializeMethod.setModifiers(serializeMethod.getModifiers() 
											 | Modifier.PUBLIC);
				serializeMethod.setBody("{return \"\";}");
				clazz.addMethod(serializeMethod);
			}
			try {
				deserializeMethod1 = clazz.getDeclaredMethod(deserializeMethodName, 
															 new CtClass[]{ctString, ctStringTokenizer});
			} catch (NotFoundException e1) {
				deserializeMethod1 = new CtMethod(ctObject, 
												  deserializeMethodName, 
												  new CtClass[]{ctString, ctStringTokenizer}, 
												  clazz);
				deserializeMethod1.setModifiers(deserializeMethod1.getModifiers() 
												| Modifier.PUBLIC 
												| Modifier.STATIC);
				deserializeMethod1.setBody("{return null;}");
				clazz.addMethod(deserializeMethod1);
			}
			try {
				deserializeMethod2 = clazz.getDeclaredMethod(deserializeMethodName, 
															 new CtClass[]{ctString});
			} catch (NotFoundException e1) {
				deserializeMethod2 = new CtMethod(ctObject, 
												  deserializeMethodName, 
												  new CtClass[]{ctString}, 
												  clazz);
				deserializeMethod2.setModifiers(deserializeMethod2.getModifiers() 
												| Modifier.PUBLIC 
												| Modifier.STATIC);
				deserializeMethod2.setBody("{return null;}");
				clazz.addMethod(deserializeMethod2);
			}
			
			////////////////////////
			// génération des méthodes
			String code1 = generateSerializeMethod(clazz);
			String code2 = generateDeserializeMethodStringStringTokenizer(clazz);
			String code3 = generateDeserializeMethodString(clazz);
			
			try {
				serializeMethod.setBody(code1);
				deserializeMethod1.setBody(code2);
				deserializeMethod2.setBody(code3);
			} catch (CannotCompileException e) {
				throw new LRGenerationException(e.getMessage());
			}
			
			clazz.writeFile(outputDir);									// a revoir
			clazz.defrost();
			
		} catch (Exception e){	// NotFoundException, CannotCompileException
			throw new LRGenerationException(e.toString() + "\n" + e.getMessage());
		}
				
	}

	//**********************************************************************//
	//*                            Serialisation                           *//
	//**********************************************************************//
	/**
	 * Génération de code de la methode "serialize()" de l'interface "LRSerializable".
	 */
	public static String generateSerializeMethod(CtClass clazz) 
	throws LRGenerationException{
		
		// code généré
		StringBuffer code;
		// le nom de la variable qui contient la chaine sérialisé dans la méthode générée "serialize()"
		String serializedStringName = "serialized";
		
		/* {
		 * 		String serializedStringName = new String();
		 */
		code = new StringBuffer("{\n");
		code.append("\tString " + serializedStringName + " = new String();\n");
		
		code.append(generateSerializeObject("this", clazz, serializedStringName));
		
		/* 		return serializedStringName; 
		 * }
		 */
		code.append("\treturn " + serializedStringName + ";\n");
		code.append("}\n");

		if(verbose){
			System.out.println("Method generated for class [" + clazz.getName() + "]");
			System.out.println("======================================================================");
			System.out.println("public String " + serializeMethodName + "()");
			System.out.println(code.toString());
			System.out.println("======================================================================");
		}

		return code.toString();
	}
	
	/**
	 * Générer la partie de code pour sérialiser l'objet
	 * @param objName Le nom de l'objet à sérialiser
	 * @param objType Le type de l'objet à sérialiser
	 * @param serializedStringName Le nom de la chaine de l'objet sérialisé dans la méthode générée "serialize()"
	 * @return Le code produit
	 * @throws LRGenerationException
	 */
	protected static String generateSerializeObject(String objName, CtClass objType, String serializedStringName) throws LRGenerationException{
		int myClassNumero = hashTab.get(objType.getName());
		
		/* serialized += objType.getName; */
		StringBuffer code = new StringBuffer("\t" + serializedStringName + " += \"" +  myClassNumero + "\";\n");
		
		// gestion de graphe cyclique, palier 5 */
		/* Integer id = Identificater.serializeGet(this);
		 * boolean registed = true;
		 * if(id == null){
		 * 		id = Identificater.serializeGet(this);
		 * 		registed = false;
		 * }
		 * serialized += "@" + id;
		 * if(!registed){
		 */
		code.append("\tInteger id = " + IdentificaterClassName + ".serializeGet(this);\n");
		code.append("\tboolean registed = true;\n");
		code.append("\tif(id == null){\n");
		code.append("\t\tid = " + IdentificaterClassName + ".serializeRegister(this);\n");
		code.append("\t\tregisted = false;\n");
		code.append("\t}\n");
		code.append("\t" + serializedStringName + " += " + delimiter + " + id.intValue();\n");
		code.append("\tif(!registed){\n");
		
		// sérialisation de tous les champs
		CtField[] fields = objType.getFields();
		try{
			for(CtField field : fields){
				// on ne serialise uniquement les champs non "final"
				if(!Modifier.isFinal(field.getModifiers()))
					code.append(generateSerializeField(field.getName(), field.getType(), serializedStringName, 2));
			}
		}catch(NotFoundException e){
			throw new LRGenerationException(e.getMessage());
		}
		
		/* }
		 */
		code.append("\t}\n");
		return code.toString();
	}
	
	/**
	 * Générer la partie de code pour sérialiser un champs d'objet
	 * @param name Le nom du champs de l'objet 
	 * @param type Le type le champs de l'objet 
	 * @param serializedStringName Le nom de la chaine de l'objet sérialisé dans la méthode générée "serialize()"
	 * @param nbTabulations Le nombre de tabulations émettre devant chaque ligne de code, sans effet sur le résulat 
	 * @return Le code produit
	 * @throws LRGenerationException
	 */
	protected static String generateSerializeField(String fieldName, CtClass fieldType, 
		String serializedStringName, int nbTabulations) throws LRGenerationException{
				
		// si c'est un champs primitif
		if(fieldType.isPrimitive())
			/* serialized += "@" + name; */
			return generateSerializePrimitiveField(fieldName, fieldType, serializedStringName, nbTabulations);
			//return "\t" + serializedStringName + " += " + delimiter + " + " + name + ";\n";
		
		// tableau ou object
		if(fieldType.isArray())
			try{
				return generateSerializeArrayField(fieldName, fieldType.getComponentType(), serializedStringName, nbTabulations);
			} catch (NotFoundException e) {
				throw new LRGenerationException(e.getMessage());
			}
		else	// object
			return generateSerializeObjectField(fieldName, fieldType, serializedStringName, nbTabulations);
			
	}
	
	/**
	 * Générer la partie de code pour sérialiser un champs de type primitif
	 * @param primitiveName Le nom du champs
	 * @param Type Le type du champs
	 * @param serializedStringName Le nom de la chaine de l'objet sérialisé dans la méthode générée "serialize()"
	 * @param nbTabulations Le nombre de tabulations à mettre devant chaque ligne de code, sans effet sur le résulat 
	 * @return Le code produit
	 * @throws LRGenerationException
	 */
	protected static String generateSerializePrimitiveField(String primitiveName, CtClass Type, String serializedStringName, int nbTabulations) throws LRGenerationException{
		String tabulations = generateTabulations(nbTabulations);
		return tabulations + serializedStringName + " += " + delimiter + " + " + primitiveName + ";\n";
	}		
	
	/**
	 * Générer la partie de code pour sérialiser un champs de type tableau d'objet
	 * @param arrayName Le nom du champs
	 * @param eltType Le type du champs
	 * @param serializedStringName Le nom de la chaine de l'objet sérialisé dans la méthode générée "serialize()"
	 * @param nbTabulations Le nombre de tabulations à mettre devant chaque ligne de code, sans effet sur le résulat 
	 * @return Le code produit
	 * @throws LRGenerationException
	 */
	protected static String generateSerializeArrayField(String arrayName, CtClass eltType, String serializedStringName, int nbTabulations) throws LRGenerationException{
		String tabulations = generateTabulations(nbTabulations);

		// optimisation, remplace le type d'éléments par un int
		Integer eltTypeNo = hashTab.get(eltType.getName());
		if(eltTypeNo == null){
			if(eltType.isArray() || eltType.isPrimitive() || eltType.isEnum() || eltType.isInterface()){
				eltTypeNo = new Integer(getNextClassNumero());
				hashTab.put(eltType.getName(), eltTypeNo.intValue());
			}else{	// object
				addLRSerializableInterface(eltType);
				eltTypeNo = hashTab.get(eltType.getName());
			}
		}	
		
		/*	serialized += '@' + \"eltType.getName()\";
		 *  if(arrayName == null){
		 *  	serialized += "@" + "null";
		 *  }else{
		 *  	serialized += "@" + arrayName.length;
		 */
		StringBuffer code = new StringBuffer(tabulations + serializedStringName + " += " + delimiter + " + " + eltTypeNo.intValue() + ";\n");
		code.append(tabulations + "if(" + arrayName + " == null){\n");
		code.append(tabulations + "\t" + serializedStringName + " += " + delimiter + " + " + serializedNull + ";\n");
		code.append(tabulations + "}else{\n");
		code.append(tabulations + "\t" + serializedStringName + " += " + delimiter + " + " + arrayName + ".length;\n");

		// le nom à donner aux ééments du tableau dans le code généré
		String eltName = arrayName + "Elt";
		
		//	!!! la syntaxe suivante n'est pas supportée
		//	for(eltType.geName() eltName : arrayName){ 
		
		/*		eltType.getName eltName;
		 *		for(int i; i < arrayName.length; i++){
		 * 			eltName = arrayName[i];
		 */
		code.append(tabulations + "\t" + eltType.getName() + " " + eltName + ";\n"); 
		code.append(tabulations + "\tfor(int i = 0; i < " + arrayName + ".length ; i++){\n");
		code.append(tabulations + "\t\t" + eltName + " = " + arrayName + "[i];\n"); 

		code.append(generateSerializeField(eltName, eltType, serializedStringName, nbTabulations + 2));
		
		/* 		}
		 * }
		 */
		code.append(tabulations + "\t}\n");	// for
		code.append(tabulations + "}\n");	// else
		return code.toString();
	}
	
	/**
	 * Générer la partie de code pour sérialiser un champs de type objet d'objet
	 * @param objName Le nom du champs
	 * @param objType Le type du champs
	 * @param serializedStringName Le nom de la chaine de l'objet sérialisé dans la méthode générée "serialize()"
	 * @param nbTabulations Le nombre de tabulations à mettre devant chaque ligne de code, sans effet sur le résulat 
	 * @return Le code produit
	 * @throws LRGenerationException
	 */
	protected static String generateSerializeObjectField(String objName, CtClass objType, String serializedStringName, int nbTabulations) throws LRGenerationException{
		String tabulations = generateTabulations(nbTabulations);
		StringBuffer code = new StringBuffer();
		
		// ajout de l'interface, si objType implemente deja cette interface, 
		// l'insertion de methode ne sera pas fait de nouveau
		addLRSerializableInterface(objType);
		
		/* if(objName == null)
		 * 		serialized += "@" + "null";
		 * else
		 * 		serialized += "@" + obj.serialize();
		 */
		code.append(tabulations + "if(" + objName + " == null)\n");
		code.append(tabulations + "\t" + serializedStringName + " += " + delimiter + " + " + serializedNull + ";\n");
		code.append(tabulations + "else\n");
		code.append(tabulations + "\t" + serializedStringName + " += " + delimiter + " + " + objName + "." + serializeMethodName + "();\n");
		return code.toString();
	}
	
	/**
	 * Générer des tabulations
	 * @param nb Le nombre de tabulations à générer
	 * @return Tabulations
	 */
	protected static String generateTabulations(int nb){
		String tabulations = "";
		for(int i = 0; i < nb; i++)
			tabulations += '\t';
		return tabulations;
	}
	
	

	//**********************************************************************//
	//*                           Désérialisation                          *//
	//**********************************************************************//
	
	/**
	 * Générer et ajouter la méthode "public static Object deserialize(String str)"
	 * @param clazz 
	 * @return Retourner la méthode générée, après l'avoir avoir à la classe  
	 * @throws LRGenerationException
	 */
	public static String generateDeserializeMethodString(CtClass clazz) throws LRGenerationException{
		// code généré
		StringBuffer code;
		// l'objet serialisé
		String serializedStringName = "$1";
		
		String tokenizerName = "tokenizer";
		
		String serializedTypeName = "serializedType";
		
		String tabulations = "\t";

		
		/* public Object deserialize(String str){
		 */
		code = new StringBuffer("{\n");

		/* if(serialized == null || serialized.equals("null"))
		 * 		return null;
		 * StringTokenizer tokenizer = new StringTokenizer(serialized, delimiter);
		 * String serializedType = tokenizer.nextToken();
		 * return deserialized(serializedType, tokenizer);
		 */
		code.append(tabulations + "if(" + serializedStringName + " == null || " 
					+ serializedStringName + ".equals(\"null\"))\n");
		code.append(tabulations + "\t" + "return null;\n");
		code.append(tabulations + "java.util.StringTokenizer " + tokenizerName + " = new java.util.StringTokenizer("
					+ serializedStringName + ", " + delimiter + ");\n");
		code.append(tabulations + "String " + serializedTypeName + ";\n");
		code.append(generateDeserializeNextToken(tokenizerName, serializedTypeName, 1));
		code.append(tabulations + "return " + deserializeMethodName + "(" 
					+ serializedTypeName + ", " + tokenizerName + ");\n");
		code.append("}\n");

		if(verbose){
			System.out.println("Method generated for class [" + clazz.getName() + "]");
			System.out.println("======================================================================");
			System.out.println("public static Object " + deserializeMethodName + "(String $1)");
			System.out.println(code.toString());
			System.out.println("======================================================================");
		}
		
		return code.toString();
	}
	
	
	public static String generateDeserializeMethodStringStringTokenizer(CtClass clazz) 
	throws LRGenerationException{
		// code généré
		StringBuffer code;
		// le type de l'objet serialisé(ex : Point, ou 1 dans le cas d'optimisation)
		String serializedTypeName = "$1";
		// le nom de la variable qui contient la chaine sérialisé dans la méthode générée "serialize()"
		String deserializedObjectName = "deserializedObject";
		// le nom de la variable qui contient le StringTokenizer
		String tokenizerName = "$2";
		
		/* public Object deserialize(String type, StringTokenizer tokenizer){
		 */
		code = new StringBuffer("{\n");

		code.append(generateDeserializeObject(deserializedObjectName, clazz, serializedTypeName, tokenizerName));
		
		/* 		return deserializedObject; 
		 * }
		 */
		code.append("\treturn " + deserializedObjectName + ";\n");
		code.append("}\n");

		if(verbose){
			System.out.println("Method generated for class [" + clazz.getName() + "]");
			System.out.println("======================================================================");
			System.out.println("public static Object " + deserializeMethodName + "(String $1, StringTokenizer $2)");
			System.out.println(code.toString());
			System.out.println("======================================================================");
		}

		return code.toString();
	}
	
		
	protected static String generateDeserializeObject(String objName, CtClass objType, String objTypeName, String tokenizerName)
	throws LRGenerationException{
		StringBuffer code = new StringBuffer();
		
		// vérifications
		/* if(tokenizerName == null || objTypeName == null || "null".equals(objTypeName))
		 * 		return null;
		 */
		code.append("\tif(" + tokenizerName + " == null || " + objTypeName + " == null || "
					+ "\"null\".equals(" + objTypeName + "))\n");
		code.append("\t\treturn null;\n");

		// a revoir ici; if((objTypeName != "null") && (objTypeName == ClassName)) ???
		/* if(1 != Integer.parseInt(objTypeClassNumero))
		 * 		return null;
		 */
		code.append("\tif(" + hashTab.get(objType.getName()) + " != Integer.parseInt(" 
					+ objTypeName + "))\n");
		code.append("\t\treturn null;\n");
		
		// instantiation
		/* objName = new instance(); 
		 */
		code.append(generateInstanceCode(objName, objType, 1));
		
		// gestion de graphe cyclique, palier 5 */
		/* String idStr;
		 * idStr = tokenizer.nextToken();
		 * Integer id = Integer.decode(idStr);
		 * if(!Identificateur.deserializeRegister(id, objName))
		 * 		return Identificateur.deserializeGet(id); 
		 */
		code.append("\tString idStr;\n");
		code.append(generateDeserializeNextToken(tokenizerName, "idStr", 1));
		code.append("\tInteger id = Integer.decode(idStr);\n");
		code.append("\tif(!" + IdentificaterClassName + ".deserializeRegister(id, " + objName + "))\n");
		code.append("\t\treturn " + IdentificaterClassName 
					+ ".deserializeGet(id);\n");
		
		// sérialisation des champs
		try{
			for(CtField field : objType.getFields()){
				// on ne serialise uniquement les champs non "final"
				if(!Modifier.isFinal(field.getModifiers()))
					code.append(generateDeserializeField(objName + "." + field.getName(), field.getType(), tokenizerName, 1));
			}
		}catch(NotFoundException e){
			throw new LRGenerationException(e.getMessage());
		}
		return code.toString();
	}
		
	protected static String generateDeserializeField(String fieldName, CtClass fieldType,  
			String tokenizerName, int nbTabulations) throws LRGenerationException{
		if(fieldType.isPrimitive()){	// primitive
			return generateDeserializePrimitiveField(fieldName, fieldType, tokenizerName, nbTabulations);
		}else if(fieldType.isArray()){	// array
			try{
				return generateDeserializeArrayField(fieldName, fieldType.getComponentType(), tokenizerName, nbTabulations);
			} catch (NotFoundException e) {
				throw new LRGenerationException(e.getMessage());
			}
		}else{							// object
			return generateDeserializeObjectField(fieldName, fieldType, tokenizerName, nbTabulations);
		}
	}
	
	protected static String generateDeserializePrimitiveField(String fieldName, CtClass fieldType, 
			String tokenizerName, int nbTabulations){
		StringBuffer code = new StringBuffer();
		String tabulations = generateTabulations(nbTabulations);
		String serializedValueName = "serializedValue";
		
		/* String serializedValue;
		 * if(!tokenizer.hasMoreToken())
		 * 		return null;
		 * serializedValue = tokenizer.nextToken();
		 */
		code.append(tabulations + "String " + serializedValueName + ";\n");
		code.append(generateDeserializeNextToken(tokenizerName, serializedValueName, nbTabulations));
		
		/* type fieldName = value */
		code.append(tabulations + fieldName + " = ");
		if(fieldType == CtClass.booleanType)
			code.append("Boolean.parseBoolean(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.byteType)
			code.append("Byte.parseByte(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.charType)
			code.append(serializedValueName + ".charAt(0);\n");
		else if(fieldType == CtClass.doubleType)
			code.append("Double.parseDouble(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.floatType)
			code.append("Float.parseFloat(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.intType)
			code.append("Integer.parseInt(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.longType)
			code.append("Long.parseLong(" + serializedValueName + ");\n");
		else if(fieldType == CtClass.shortType)
			code.append("Short.parseShort(" + serializedValueName + ");\n");
//		else if(fieldType == CtClass.voidType)
//			code.append("void");
//		else error
		
		return code.toString(); 
	}
	
	protected static String generateDeserializeArrayField(String arrayName, CtClass eltType, 
			String tokenizerName, int nbTabulations) throws LRGenerationException{
		StringBuffer code = new StringBuffer();
		String tabulations = generateTabulations(nbTabulations);
		String serializedEltTypeName = "serializedEltType";			// extrait de la objet serialisé
		String eltTypeName = eltType.getName();						// type attendu
		String dataName = "data";									// le token juste après le type, il peut être null ou length
		String lengthName = "length";
		String eltName = arrayName.replace('.', '_') + "Elt";
		
		// vérification de type
		/* String serializedEltType;
		 * if(!tokenizer.hasMoreToken())
		 * 		return null;
		 * serializedEltType = tokenizer.nextToken();
		 */
		code.append(tabulations + "String " + serializedEltTypeName + ";\n");
		code.append(generateDeserializeNextToken(tokenizerName, serializedEltTypeName, nbTabulations));
		
		// optimisation, type name -> int pour les tableaux
		Integer eltTypeNo = hashTab.get(eltTypeName);
		if(eltTypeNo == null)
			throw new LRGenerationException("Error : Unrecognised type " + eltTypeName);
		/* if(eltTypeNo != Integer.parseInt(serializedEltTypeNo)
		 * 		return null;
		 */
		code.append(tabulations + "if(" + eltTypeNo.intValue() + " != " 
					+ "Integer.parseInt(" + serializedEltTypeName + "))\n");
		code.append(tabulations + "\treturn null;\n");
		
		// vérification null ou length
		/* String data = new String();
		 * if(!tokenizer.hasMoreToken())
		 * 		return null;
		 * data = tokenizer.nextToken();
		 */
		code.append(tabulations + "String " + dataName + " = new String();\n");
		code.append(generateDeserializeNextToken(tokenizerName, dataName, nbTabulations));
		
		/* if("null".equals(data))
		 * 		array = null;
		 * else{
		 * 		int length = Integer.valueOf(data).intValue;	// exception
		 * 		array = new EltType[length];
		 * 		EltType elt;
		 * 		for(int i = 0; i < length; i++){
		 */
		code.append(tabulations + "if(" + serializedNull + ".equals(" + dataName + ")){\n");
		code.append(tabulations + "\t" + arrayName + " = null;\n");
		code.append(tabulations + "}else{\n");
		code.append(tabulations + "\tint " + lengthName + " = Integer.parseInt(" + dataName + ");\n");
		
		int index = eltTypeName.indexOf('[');
		String arrayTypeWithDim;
		if(index == -1)
			// si une dimension : eltType[length]
			arrayTypeWithDim = eltTypeName + "[" + lengthName + "]";
		else
			// si plusieurs dimensions : eltType[length][]
			arrayTypeWithDim = eltTypeName.substring(0, index+1) + lengthName + eltTypeName.substring(index+1) + "[]";
		//System.out.println("!!!!!!" + eltTypeName + "!!!!!!!!! " + index + "!!!!!!!!!!!!!!!!!!!!" + arrayTypeWithDim);
		code.append(tabulations + "\t" + arrayName + " = new " + arrayTypeWithDim + ";\n");
		code.append(tabulations + "\t" + eltTypeName + " " + eltName + ";\n");
		code.append(tabulations + "\tfor(int i = 0; i < " + lengthName + "; i++){\n");
		
		//generate
		////code.append(generateDeserializeField(eltName, eltType, instanceName, tokenizerName, nbTabulations))

		code.append(generateDeserializeField(eltName, eltType, tokenizerName, nbTabulations + 2));
		
		/* 			array[i] = elt;
		 */
		code.append(tabulations + "\t\t" + arrayName + "[i] = " + eltName + ";\n");
		code.append(tabulations + "\t}\n");
		code.append(tabulations + "}\n");
	
		return code.toString();
	}
	
	protected static String generateDeserializeObjectField(String objName, CtClass objType, 
			String tokenizerName, int nbTabulations) {
		String tabulations = generateTabulations(nbTabulations);
		String objTypeName = objName.replace('.', '_') + "Type";
		StringBuffer code = new StringBuffer();
		
		/* 	String objType = tokenizer.nextToken();
		 * 	if("null".equals(objType));
		 * 		obj = null;
		 * 	else if(1 == Integer.parseInt(objType)){
		 * 		obj = Point.deserialize(objType, tokenizer);
		 * 	}else{
		 * 		obj = aladyn.dispatcher.Dispatcher.type(objType, tokenizer);
		 * 	}
		 */
		code.append(tabulations + "String " + objTypeName + ";\n");
		code.append(generateDeserializeNextToken(tokenizerName, objTypeName, nbTabulations));
		code.append(tabulations + "if(\"null\".equals(" + objTypeName + "))\n");
		code.append(tabulations + "\t" + objName + " = null;\n");
		code.append(tabulations + "else if(" + hashTab.get(objType.getName()) + " == Integer.parseInt("
					+ objTypeName + ")){\n");
		code.append(tabulations + "\t" + objName + " = " + objType.getName() + "." 
				+ deserializeMethodName	+ "(" + objTypeName + ", " + tokenizerName + ");\n");
		code.append(tabulations + "}else{\n");
		code.append(tabulations + "\t" + objName + " = aladyn.dispatcher.Dispatcher." 
				+ typeMethodName + "(" + objTypeName + ", " + tokenizerName + ");\n");
		code.append(tabulations + "}\n");
		return code.toString();
	}
	
	/**
	 * Générer un bout de code pour récupérer le prochain token, 
	 * et de l'affecter à la variable de nom "varName"
	 * @param tokenizerName
	 * @param varName
	 * @param nbTabulations
	 * @return
	 */
	protected static String generateDeserializeNextToken(String tokenizerName, String varName, int nbTabulations){
		/* if(!tokenizer.hasMoreToken())
		 * 		return null;
		 * varName = tokenizer.nextToken();
		 */
		String tabulations = generateTabulations(nbTabulations);
		return tabulations + "if(!" + tokenizerName + ".hasMoreTokens())\n"
			+ tabulations + "\treturn null;\n"
			+ tabulations + varName + " = " + tokenizerName + ".nextToken();\n";
	}
	
	/**
	 * Générer un bout de code pour instancier une classe
	 * @param instanceName
	 * @param type
	 * @param nbTabulations
	 * @return
	 * @throws LRGenerationException
	 */
	protected static String generateInstanceCode(String instanceName, CtClass type, int nbTabulations) 
	throws LRGenerationException{
		String booleanDefaultValue = "false";
		String charDefaultValue = "\'a\'";
		String numericDefaultValue = "0";
		String objectDefaultVale = "null";
		
		try{
			StringBuffer code = new StringBuffer();
			String tabulations = generateTabulations(nbTabulations);
			String typeName = type.getName();
			
			/* Type instance = new type( */
			code.append(tabulations + typeName + " " + instanceName + " = new " + typeName + "(");
			
			CtConstructor[] constructors = type.getConstructors();
			if(constructors.length != 0){	// il y a au moins un constructeur déclaré
				CtConstructor constructor = type.getConstructors()[0];
				CtClass[] paramTab = constructor.getParameterTypes();
				
				CtClass param;
				for(int i = 0; i < paramTab.length; i++){
					param = paramTab[i];
					if(i != 0)
						code.append(", ");
					
					if(param.isPrimitive()){
						if(param == CtClass.booleanType) 	// boolean
							code.append(booleanDefaultValue);
						else if(param == CtClass.charType)	// char
							code.append(charDefaultValue);
						else								// char, int, float, double...
							code.append(numericDefaultValue);
					}else{		// array, object
						code.append("(" + param.getName() + ")" + objectDefaultVale);
					}				
				}
			} // si aucun constructeur, on peut appeler le constructeur par défaut sans argument
			code.append(");\n");
			return code.toString();
		}catch(NotFoundException e){
			throw new LRGenerationException(e.getMessage());
		}
	}
	
	//**********************************************************************//
	//*                             Dispatcher                             *//
	//**********************************************************************//
	
	public static final String typeMethodName = "type";

	/**
	 * Le nom complet de la class Dispatcher
	 */
	public static final String dispatcherName = "aladyn.dispatcher.Dispatcher";
	
	
	/**
	 * Générer la méthode "public static Object type(String)" de la class Dispatcher
	 * @throws LRGenerationException
	 */
	public static void generateTypeMethodString() 
	throws LRGenerationException {
		CtClass dispatcher;
		try {
			dispatcher = LoadCtClass("aladyn.dispatcher.Dispatcher");
		} catch (NotFoundException e) {
			throw new LRGenerationException(e.getMessage());
		}
		
		StringBuffer code = new StringBuffer("");
		String tabulation = "\t";
		String arg1 = "$1";
		// le nom de la variable qui contient le StringTokenizer
		String tokenizerName = "tokenizer";
		String serializedTypeName = "serializedType";
		
		/* 	{
		 * 		StringTokenizer tokenizer = new StringTokenizer($1, "@");
		 * 		String type;
		 */
		code.append("{\n");
		code.append(tabulation + "java.util.StringTokenizer " + tokenizerName +
					" = new java.util.StringTokenizer(" + arg1 + ", " + delimiter + ");\n");
		code.append(tabulation + "String " + serializedTypeName + ";\n");
		
		/* 		type = tokenizer.nextToken();
		 */
		code.append(generateDeserializeNextToken(tokenizerName, serializedTypeName, 1));
		
		/* 		return type(type, tokenizer);
		 *	}
		 */
		code.append(tabulation + "return " + typeMethodName + "(" + serializedTypeName
					+ ", " + tokenizerName + ");\n");
		code.append("}\n");
		
		if(verbose){
			System.out.println("Method generated for class [" + dispatcher.getName() + "]");
			System.out.println("======================================================================");
			System.out.println("public static Object " + typeMethodName + "(String $1)");
			System.out.println(code.toString());
			System.out.println("======================================================================");
		}
		
		CtMethod method;
		try {
			CtClass ctString = LoadCtClass("java.lang.String");
			CtClass[] params = {ctString};
			method = dispatcher.getDeclaredMethod(typeMethodName, params);
			method.setBody(code.toString());
			dispatcher.writeFile(outputDir);
			dispatcher.defrost();
		} catch (Exception e) {		// NotFoundException, CanNotCompileException, IOException
			throw new LRGenerationException(e.getMessage());
		}
	}
	
	/**
	 * Générer la méthode "public static Object type(String, StringTokenizer)" de la class Dispatcher
	 * @throws LRGenerationException
	 */	
	public static void generateTypeMethodStringStringTokenizer() 
	throws LRGenerationException {
		CtClass dispatcher;
		try {
			dispatcher = LoadCtClass("aladyn.dispatcher.Dispatcher");
		} catch (NotFoundException e) {
			throw new LRGenerationException(e.getMessage());
		}
		
		StringBuffer code = new StringBuffer("");
		String tabulation = "\t";
		// le nom de la variable qui contient le StringTokenizer
		String serializedTypeName = "$1";
		String tokenizerName = "$2";
		
		/* {
		 * 	 if("Point".equals(type)
		 *     return Point.deserialize(type, tokenizer);
		 */
		code.append("{\n");
		
		ArrayList<String> primitiveTypeNames = new ArrayList<String>();
		primitiveTypeNames.add("boolean");
		primitiveTypeNames.add("byte");
		primitiveTypeNames.add("char");
		primitiveTypeNames.add("short"); 
		primitiveTypeNames.add("int");
		primitiveTypeNames.add("long");
		primitiveTypeNames.add("float");
		primitiveTypeNames.add("double");
		primitiveTypeNames.add("void");
		
		for(String className : hashTab.keySet()){
			// dans le cas de tableau et type primitif, on les ajoute pas "type()"
			if(className.indexOf('[') == -1 && !primitiveTypeNames.contains(className)){	
				code.append(tabulation + "if(" + hashTab.get(className) + " == " 
							+ "Integer.parseInt(" + serializedTypeName + "))\n");
				code.append(tabulation + "\treturn " + className + ".deserialize(" 
							+ serializedTypeName + ", " + tokenizerName + ");\n");
			}
		}
		
		// class non trouvée
		/* return null;
		 */
		code.append(tabulation + "return null;\n");
		code.append("}\n");
		
		if(verbose){
			System.out.println("Method generated for class [" + dispatcher.getName() + "]");
			System.out.println("======================================================================");
			System.out.println("public static Object " + typeMethodName + "(String $1, StringTokenizer $2)");
			System.out.println(code.toString());
			System.out.println("======================================================================");
		}
		
		CtMethod method;
		try {
			CtClass ctString = LoadCtClass("java.lang.String");
			CtClass ctStringTokenizer = LoadCtClass("java.util.StringTokenizer");
			CtClass[] params = {ctString, ctStringTokenizer};
			method = dispatcher.getDeclaredMethod(typeMethodName, params);
			
			method.setBody(code.toString());
			dispatcher.writeFile(outputDir);
			dispatcher.defrost();
		} catch (Exception e) {		// NotFoundException, CanNotCompileException, IOException
			throw new LRGenerationException(e.getMessage());
		}
	}
	
	protected static int getNextClassNumero(){
		return classNumero++;
	}
	
	//**********************************************************************//
	//*                           Option Verbose                           *//
	//**********************************************************************//
	
	protected static boolean verbose = false;
	
	public static boolean getVerbose(){
		return verbose;
	}
	
	public static void setVerbose(boolean v){
		verbose = v;
	}
}
		
		
		
		
		
		
		